home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-07-10 | 36.6 KB | 1,122 lines |
-
- // TileSlide v1.0
- // By Kevin Brain (ksbrain@zeus.UWaterloo.ca)
- // with SoundGenerator class by Ali Ozer
- // Released to the public domain September, 1991
-
- #import "TileControl.h"
-
- #import <appkit/nextstd.h> //imports math.h,stdio.h,libc.h
- // (libc.h imports string.h)
- #import <appkit/Box.h>
- #import <appkit/Button.h>
- #import <appkit/Cell.h>
- #import <appkit/Text.h> // NXOrderStrings
- #import <appkit/Matrix.h> // cellAt:: method
- #import <appkit/Panel.h> // NXRunAlertPanel
- #import <appkit/OpenPanel.h> // for loading background
- #import <appkit/Application.h> // NX_BASETHRESHOLD
- #import <appkit/FontManager.h> // for changing size of button fonts
- #import <appkit/publicWraps.h> // for NXConvertWinNumToGlobal
- #import <appkit/Listener.h> // for NX_WORKSPACEREQUEST
- #import <appkit/Speaker.h> // for setSendPort
- #import <defaults.h> // for using defaults system
-
- #import <next/machparam.h> // for DELAY (playing ending tune)
-
- // The scheme for the "music" is to play 2 note chords, where the notes are
- // selected from arrays of chords. The musical key changes every NOTESPERBAR notes.
- // The two arrays rowNotes and colNotes store the pitches. (The notes of the
- // rowNotes array are generally lower in pitch). A note from each array is
- // played whenever a tile is moved. The note from the rowNotes array is selected
- // by the row number clicked, while the notes within a given key (row) of the
- // colNotes array are played sequentially.
- #define NUMBEROFBARS 8
- #define NOTESPERBAR 4
- #define COMPUTEBAR ((int)((numberOfMoves%(NUMBEROFBARS*NOTESPERBAR)) /NOTESPERBAR))
- #define SEQUENTIALNOTES ((numberOfMoves%(NUMBEROFBARS*NOTESPERBAR)) %NOTESPERBAR)
- #define ROWNOTE(k,n) (MKKeyNumToFreq (rowNotes[(k%NUMBEROFBARS)][(n%5)]))
- #define COLNOTE(k,n) (MKKeyNumToFreq (colNotes[(k%NUMBEROFBARS)][(n%5)]))
-
- @implementation TileControl
-
- - appDidInit:sender
- // responds as application's delegate
- {
- NXRect contentViewFrame;
- NXSize theSize;
- char titleTemp[3];
- const char *tmpstr;
- int i,row,col,x,y;
- int tempIconPosition;
- unsigned int windowNum;
- id speaker = [NXApp appSpeaker];
-
- static NXDefaultsVector TileSlideDefaults = {
- {"Picture", ""},
- {"Difficulty", "Normal"},
- {"UsePictures", "YES"},
- {"UseNumbers", "NO"},
- {"EasyBests", "999,999,999,999"},
- {"NormalBests", "999,999,999,999"},
- {"HardBests", "999,999,999,999"},
- {"BoardLocation","314 314"},
- {NULL}};
-
- NXRegisterDefaults([NXApp appName], TileSlideDefaults);
-
- tileSlideCV = [slideWindowOut contentView];
- infoPanelCV = [infoPanelOut contentView];
- /* set initial difficulty */
- tmpstr = NXGetDefaultValue ([NXApp appName], "Difficulty");
- if (sscanf(tmpstr, "%d", &NumTilesX) != 1) NumTilesX = 4;
- NumTilesY = NumTilesX;
-
- /* retrieve bests for four challenges in each difficulty */
- tmpstr = NXGetDefaultValue ([NXApp appName], "EasyBests");
- if (sscanf(tmpstr, "%d %d %d %d", &bests[0][0],&bests[0][1],&bests[0][2],&bests[0][3]) != 4)
- for (i=0;i<4;i++) bests[0][i] = MAXMOVES;
- tmpstr = NXGetDefaultValue ([NXApp appName], "NormalBests");
- if (sscanf(tmpstr, "%d %d %d %d", &bests[1][0],&bests[1][1],&bests[1][2],&bests[1][3]) != 4)
- for (i=0;i<4;i++) bests[1][i] = MAXMOVES;
- tmpstr = NXGetDefaultValue ([NXApp appName], "HardBests");
- if (sscanf(tmpstr, "%d %d %d %d", &bests[2][0],&bests[2][1],&bests[2][2],&bests[2][3]) != 4)
- for (i=0;i<4;i++) bests[2][i] = MAXMOVES;
- for (row=0;row < 3;row++)
- for (col=0; col < 4; col++)
- if (bests[row][col] < MAXMOVES)
- [[bestsMatrixOut cellAt:row:col] setIntValue:bests[row][col]];
- else
- [[bestsMatrixOut cellAt:row:col] setStringValue:"–"];
-
- /* initialize info panel animation variables */
- [infoPanelCV getFrame:&contentViewFrame];
- stripe1Positiony = 5;
- stripe2Positiony = (contentViewFrame.size.height-33);
- stripe1Positionx = 5;
- stripe2Positionx = (contentViewFrame.size.width-33);
- stripe1Movement = 5;
- stripe2Movement = -5;
-
- tmpstr = NXGetDefaultValue ([NXApp appName], "Picture");
- if ([self initAndCheckPicture:(char *)tmpstr] == NO) {
- pictureLoaded = NO;
- usePictures = NO;
- useNumbers = YES;
- [useNumbersSwitch setEnabled:NO];
- [useNumbersSwitch setIntValue:1];
- [usePicturesSwitch setEnabled:NO];
- [usePicturesSwitch setIntValue:0];
- tempIconPosition = NX_TITLEONLY;
- }
- else {
-
- [picture getSize:(NXSize *)&theSize];
- tmpstr = NXGetDefaultValue ([NXApp appName], "UsePictures");
-
- if (NXOrderStrings((unsigned char *) "YES",(unsigned char *) tmpstr, YES, 4, NULL) == 0) {
- usePictures = YES;
- [usePicturesSwitch setIntValue:1];
- }
- else {
- usePictures = NO;
- [usePicturesSwitch setIntValue:0];
- }
-
- if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)
- && ([usePicturesSwitch intValue] == 1))
- [useNumbersSwitch setEnabled:YES];
- else
- [useNumbersSwitch setEnabled:NO];
-
- tmpstr = NXGetDefaultValue ([NXApp appName], "UseNumbers");
- if ((NXOrderStrings((unsigned char *) "YES",(unsigned char *) tmpstr, YES, 4, NULL) == 0)
- && (theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) {
- useNumbers = YES;
- [useNumbersSwitch setIntValue:1];
- [usePicturesSwitch setEnabled:YES];
- if (usePictures == YES)
- tempIconPosition = NX_ICONOVERLAPS;
- else
- tempIconPosition = NX_TITLEONLY;
- }
- else {
- useNumbers = NO;
- [useNumbersSwitch setIntValue:0];
- [usePicturesSwitch setEnabled:NO];
- if (usePictures == YES)
- tempIconPosition = NX_ICONONLY;
- else
- tempIconPosition = NX_TITLEONLY;
- }
- tempIconPosition = NX_ICONONLY;
- }
- myFontManager = [FontManager new];
- /* create the box that holds the tiles */
- tileBox = [Box new];
- [tileBox setBorderType:NX_BEZEL];
- [tileBox setTitlePosition:NX_NOTITLE];
- [tileBox setOffsets:(NXCoord)0 :(NXCoord)0];
- /* create a box for erasing the old button position when a button is moved */
- clearBox = [Box new];
- [clearBox setBorderType:NX_NOBORDER];
- [clearBox setTitlePosition:NX_NOTITLE];
- [clearBox setOffsets:(NXCoord)0 :(NXCoord)0];
-
- for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) {
- tile[i] = [Button new];
- [tile[i] setAutodisplay:NO];
- sprintf(titleTemp,"%d",i+1);
- [tile[i] setTitle:titleTemp];
- [tile[i] setIconPosition:tempIconPosition];
- }
-
- /* Create a matrix of invisible buttons that detect button presses */
- /* Tag numbers of the form (row * MAXSIZEX + col) tell the slideTile: */
- /* method where the button was pressed */
- for (row=0; row<MAXSIZEY; row++)
- for (col=0; col<MAXSIZEX; col++) {
- tileInPosition[row][col].invisibleID = [Button new];
- [tileInPosition[row][col].invisibleID setTransparent:(BOOL)YES];
- [tileInPosition[row][col].invisibleID setTag:(row * MAXSIZEX + col)];
- [tileInPosition[row][col].invisibleID setTarget:self];
- [tileInPosition[row][col].invisibleID setAction:@selector(slideTile:)];
- }
-
- /* add the tile to the tileBox */
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++) {
- [tileBox addSubview:tile[row * NumTilesX + col]];
- }
- [tile[NumTilesX*NumTilesY-1] removeFromSuperview]; // remove one for the space
-
- /* add the invisible tiles to the tileBox (added last so they are on top) */
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++) {
- [tileBox addSubview:tileInPosition[row][col].invisibleID];
- }
- [tileSlideCV addSubview:tileBox];
- tmpstr = NXGetDefaultValue ([NXApp appName], "BoardLocation");
- if (sscanf(tmpstr, "%d %d", &x,&y) != 2) { x = 314; y = 314; }
- [slideWindowOut moveTo:(NXCoord)x :(NXCoord)y];
- [self shuffle:mixPattern];
- [self randomMix:self];
- [self sizeSlideBoard];
- [slideWindowOut orderFront:self];
-
- /* register the tile slide window with the workspace (to accept pictures) */
- /* (see Controller.m from Acceptor demo app) */
- listener = [Listener new];
- [listener setDelegate:self];
- [listener usePrivatePort];
- [listener addPort];
- NXConvertWinNumToGlobal([slideWindowOut windowNum], &windowNum);
- [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
- [speaker registerWindow:windowNum toPort:[listener listenPort]];
- /* enable the soundGenerator object (get the DSP) */
- if ([soundGenerator enable] == NO) {
- NXRunAlertPanel(NULL,"Can't open the DSP.", "That's life.", NULL, NULL);
- [soundSwitch setIntValue:0];
- }
-
- return self;
- }
-
- - appDidHide:app
- // responds as application's delegate
- {
- [soundGenerator disable];
- if ([infoPanelOut isVisible] == YES) // if info panel is open, animation is happening
- DPSRemoveTimedEntry (infoTimer); // STOP IT!
- return self;
- }
-
- - appDidUnhide:app
- // responds as application's delegate
- {
- void infoAnimatorTimeout();
- if ([soundGenerator enable] == NO) {
- NXRunAlertPanel(NULL,"Can't open the DSP.", "That's life.", NULL, NULL);
- [soundSwitch setIntValue:0];
- }
- if ([infoPanelOut isVisible] == YES) // if info panel is open, restart animation
- infoTimer = DPSAddTimedEntry((double) INFOTIMERPERIOD, &infoAnimatorTimeout, self, NX_BASETHRESHOLD);
- return self;
- }
-
- - init
- // Initialize a few variables. Some of these things could be changed while
- // running if a user interface to them is added (perhaps a preferrences panel)
- {
- [super init];
- movementSteps = 3; // number of animation steps when moving tiles
- currentStep = 0;
- slidePeriod = .01; // seconds of delay between animation frames
- topMargin = 15; // does not include window's titlebar
- bottomMargin = 53; // between sizebar and bottom of tilebox
- leftMargin = 10;
- rightMargin = 15;
- interTileDistance = 2;
- buttonBorderWidth = 3; // lines which make up border of tile buttons
- pictureType = TYPENONE;
- infoTimer = NULL;
- mixPattern = RANDOMFLAG;
- solved = NO;
-
- return self;
- }
-
- - free
- {
- int i,row,col;
- [picture free];
- [tileBox free];
- [clearBox free];
- [myFontManager free];
- for (i=0; i<(MAXSIZEX*MAXSIZEY); i++)
- [tile[i] free];
- for (row=0; row<MAXSIZEY; row++)
- for (col=0; col<MAXSIZEX; col++)
- [tileInPosition[row][col].invisibleID free];
- return [super free];
- }
-
- - openInfoPanel:sender
- // show info panel and start info panel animation
- {
- void infoAnimatorTimeout();
- [infoPanelOut orderFront:self];
- if (!infoTimer)
- infoTimer = DPSAddTimedEntry((double) INFOTIMERPERIOD, &infoAnimatorTimeout, self, NX_BASETHRESHOLD);
- return self;
- }
-
- - windowWillClose:sender
- // respond as delegate for info panel
- {
- DPSRemoveTimedEntry (infoTimer);
- infoTimer = NULL;
-
- return self;
- }
-
- - windowDidMove:sender;
- // save new board position in defaults database
- {
- NXRect theRect;
- char str[20];
-
- [slideWindowOut getFrame:(NXRect *)&theRect];
- sprintf (str, "%d %d",(int)theRect.origin.x,(int)theRect.origin.y);
- NXWriteDefault ([NXApp appName], "BoardLocation", str);
- return self;
- }
-
- void infoAnimatorTimeout (DPSTimedEntry timedEntry, double timeNow, void *data)
- // dummy method to invoke changeInfoPanel method in response to info panel animation timeout
- {
- [(id)data changeInfoPanel];
- }
-
- - (void)changeInfoPanel
- // Move buttons on info panel to create animation!!
- {
- NXRect contentViewFrame;
-
- [infoPanelCV getFrame:&contentViewFrame];
- [stripe1Blanker moveTo:(NXCoord)stripe1Positionx:(NXCoord)stripe1Positiony];
- if (stripe1Positiony+stripe1Movement < 5)
- stripe1Movement = -stripe1Movement;
- if (stripe1Positiony+stripe1Movement+33 > contentViewFrame.size.height)
- stripe1Movement = -stripe1Movement;
- stripe1Positiony += stripe1Movement;
- [stripe1 moveTo:(NXCoord)stripe1Positionx :(NXCoord)stripe1Positiony];
- [stripe1Blanker display];
- [stripe1 display];
-
- [stripe2Blanker moveTo:(NXCoord)stripe2Positionx:(NXCoord)stripe2Positiony];
- if (stripe2Positiony+stripe2Movement < 5)
- stripe2Movement = -stripe2Movement;
- if (stripe2Positiony+stripe2Movement+33 > contentViewFrame.size.height)
- stripe2Movement = -stripe2Movement;
- stripe2Positiony += stripe2Movement;
- [stripe2 moveTo:(NXCoord)stripe2Positionx :(NXCoord)stripe2Positiony];
- [stripe2Blanker display];
- [stripe2 display];
- return;
- }
-
- - infoPanelNotes:sender
- // plays a random 2-note chord when a button on info panel is pressed
- {
- int key;
-
- key = random() % NUMBEROFBARS;
-
-
- return self;
- }
-
- - openBackground:sender
- // select background to load via open panel
- {
- char *theFile,*theDirectory;
- const char *const *returnedFileList;
- char **fileTypeList;
- char *typeList[3];
- NXSize theSize;
- int length;
-
- typeList[0] = "tiff";
- typeList[1] = "eps";
- typeList[2] = 0;
- fileTypeList = typeList;
-
- myOpenPanel = [OpenPanel new];
- [myOpenPanel allowMultipleFiles:(BOOL)NO];
- if ([myOpenPanel runModalForTypes:(const char *const *)fileTypeList] == 0)
- return self; // CANCEL selected
- returnedFileList = [myOpenPanel filenames];
- if (returnedFileList == NULL) return self;
- theFile = (char *) *returnedFileList;
- theDirectory = (char *) [myOpenPanel directory];
- length = (strlen(theFile) + strlen(theDirectory)+1);
- if (filePathLength <= length) {
- if (filePath) {
- free(filePath);
- }
- filePath = (char *)malloc(length + 1);
- filePathLength = length;
- }
-
- strcpy(filePath, theDirectory);
- strcat(filePath, "/");
- strcat(filePath, theFile);
-
- if ([self initAndCheckPicture:(char *)filePath] == NO)
- return self; // selected file is not loadable
- else {
- if (usePictures == NO){
- usePictures = YES;
- [usePicturesSwitch setIntValue:1];
- NXWriteDefault ([NXApp appName], "UsePictures", "YES");
- }
- [picture getSize:&theSize];
- [picture getSize:(NXSize *)&theSize];
- if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) {
- [useNumbersSwitch setEnabled:YES];
- if (useNumbers == YES)
- [usePicturesSwitch setEnabled:YES];
- }
- else { // picture too small to allow using numbers
- useNumbers = NO;
- [useNumbersSwitch setEnabled:NO];
- [useNumbersSwitch setIntValue:0];
- [usePicturesSwitch setEnabled:NO];
- }
- [self sizeSlideBoard];
- }
- return self;
- }
-
- - togglePictures:sender
- // Toggles pictures on and off. Receives action messages from the "Picture" switch.
- {
- if ([sender intValue] == 1) {
- usePictures = YES;
- NXWriteDefault ([NXApp appName], "UsePictures", "YES");
- [useNumbersSwitch setEnabled:YES];
- }
- else {
- usePictures = NO;
- NXWriteDefault ([NXApp appName], "UsePictures", "NO");
- [useNumbersSwitch setEnabled:NO];
- };
- [self sizeSlideBoard];
-
- return self;
- }
-
- - toggleNumbers:sender
- // Toggles visibility of numbers on and off.
- // Receives action messages from the "Numbers" switch.
- {
- if ([sender intValue] == 1) {
- useNumbers = YES;
- NXWriteDefault ([NXApp appName], "UseNumbers", "YES");
- if (pictureLoaded == YES)
- [usePicturesSwitch setEnabled:YES];
- }
- else {
- useNumbers = NO;
- NXWriteDefault ([NXApp appName], "UseNumbers", "NO");
- [usePicturesSwitch setEnabled:NO];
- };
- [self sizeSlideBoard];
-
- return self;
- }
-
- - sizeSlideBoard
- // Draws the tile board at the correct size, with the NumTilesX * NumTilesY tiles
- // Determines the size from the current size of "picture" if the "Picture" switch
- // is checked, and from the size of the window if not.
- {
- NXRect tempRect;
- NXSize tempSize;
- NXPoint tempPoint;
- id tempImage,newFont;
- int i,row,col,minSize;
- float tempFontSize;
-
- if (usePictures == YES) {
- [slideWindowOut disableFlushWindow];
- [picture getSize:&tempSize];
- sizex = (int)((tempSize.width/NumTilesX)-buttonBorderWidth);
- sizey = (int)((tempSize.height/NumTilesY)-buttonBorderWidth);
- tempSize.width = (NXCoord)sizex;
- tempSize.height = (NXCoord)sizey;
- tempRect.size = tempSize;
- if (sizex>sizey) minSize = sizey;
- else minSize = sizex;
- tempFontSize = (float)((minSize-5+buttonBorderWidth)/1.6);
- newFont = [myFontManager findFont:(const char *)"Helvetica" traits:
- (NXFontTraitMask)NX_UNBOLD weight:(int)0 size:tempFontSize];
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++){
- tempPoint.x = (NXCoord)((sizex+buttonBorderWidth) * col);
- tileInPosition[row][col].xPosition = tempPoint.x;
- tempPoint.y = (NXCoord)((sizey+buttonBorderWidth) * (NumTilesY-row-1));
- tileInPosition[row][col].yPosition = tempPoint.y;
- tempRect.origin = tempPoint;
- tempImage = [[NXImage alloc] initFromImage:(NXImage *)picture rect:(const NXRect *)&tempRect];
- [tile[row*NumTilesX+col] setImage:tempImage];
-
- [tile[row*NumTilesX+col] sizeTo:(NXCoord)(sizex+buttonBorderWidth)
- :(NXCoord)(sizey+buttonBorderWidth)];
- [tile[tileInPosition[row][col].tileNumber] moveTo:
- (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
- [tileInPosition[row][col].invisibleID moveTo:
- (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
- [tileInPosition[row][col].invisibleID sizeTo:
- (NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)];
- if (useNumbers == YES)
- [tile[row*NumTilesX+col] setIconPosition:NX_ICONOVERLAPS];
- else
- [tile[row*NumTilesX+col] setIconPosition:NX_ICONONLY];
- }
- [clearBox sizeTo:(NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)];
- [tileBox setBorderType:NX_BEZEL];
- [tileBox sizeToFit];
- [tileBox getFrame:&tempRect];
- windowSize.width = (NXCoord)(tempRect.size.width + leftMargin + rightMargin);
- windowSize.height = (NXCoord)(tempRect.size.height + topMargin + bottomMargin);
- [slideWindowOut sizeWindow:windowSize.width :windowSize.height];
- [tileBox moveTo:(NXCoord)leftMargin :(NXCoord)bottomMargin];
-
- // I don't think the display in the following loop should be necessary, but set font
- // doesn't seem to work unless you do a display after setting icon position
- if (useNumbers == YES) {
- for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) {
- [tile[i] display];
- [tile[i] setFont:newFont];
- }
- }
-
- [slideWindowOut reenableFlushWindow];
- [slideWindowOut display];
- }
-
- else {
- [slideWindowOut disableFlushWindow];
- [tileSlideCV getFrame:&tempRect];
- sizex = (int) ((tempRect.size.width - leftMargin - rightMargin -((NumTilesX-1) * interTileDistance))/NumTilesX);
- sizey = (int) ((tempRect.size.height - topMargin - bottomMargin -((NumTilesY-1) * interTileDistance))/NumTilesY);
-
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++){
- tileInPosition[row][col].xPosition =(NXCoord)((sizex+interTileDistance) * col);
- tileInPosition[row][col].yPosition =(NXCoord)((sizey+interTileDistance) * (NumTilesY-row-1));
- [tile[tileInPosition[row][col].tileNumber] moveTo:
- (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
- [tileInPosition[row][col].invisibleID moveTo:
- (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
- [tile[tileInPosition[row][col].tileNumber] sizeTo:(NXCoord)sizex :(NXCoord)sizey];
- [tileInPosition[row][col].invisibleID sizeTo:(NXCoord)sizex :(NXCoord)sizey];
- }
- if (sizex>sizey) minSize = sizey;
- else minSize = sizex;
- for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) {
- [tile[i] setIconPosition:NX_TITLEONLY];
- [tile[i] display];
- [tile[i] setFont:[myFontManager findFont:(const char *)"Helvetica" traits:(NXFontTraitMask)NX_UNBOLD weight:(int)0 size:(float)((minSize-5)/1.6)]];
- }
-
- [clearBox sizeTo:(NXCoord)sizex :(NXCoord)sizey];
- [tileBox setBorderType:NX_BEZEL];
- [tileBox sizeToFit];
- [tileBox moveTo:(NXCoord)leftMargin :(NXCoord)bottomMargin];
- [slideWindowOut reenableFlushWindow];
- [slideWindowOut display];
- }
-
- return self;
- }
-
- - slideTile:sender
- // Receives action messages from the invisible buttons over the tiles
- // Starts sliding animation timer if sliding is to be done.
- {
- void slideTimeout ();
- int row,col;
-
- if (solved == YES) return self;
- if (sliding == YES) return self;
- row = (int) [sender tag]/MAXSIZEX;
- col = (int) [sender tag]-(row*MAXSIZEX);
- if ((rowOfSpace != row) && (colOfSpace != col)) return self;
- if ((rowOfSpace == row) && (colOfSpace == col)) return self;
-
-
- if (rowOfSpace == row) {
- slideRowOrCol = ROW;
- howManyToMove = colOfSpace - col;
- }
- if (colOfSpace == col) {
- slideRowOrCol = COL;
- howManyToMove = rowOfSpace - row;
- }
- numberOfMoves++;
- [movesOut setIntValue:numberOfMoves];
-
- sliding = (BOOL)YES;
- fromRow = row;
- fromCol = col;
- slideTimer = DPSAddTimedEntry((double) slidePeriod, &slideTimeout, self, NX_BASETHRESHOLD);
-
- return self;
- }
-
- void slideTimeout (DPSTimedEntry timedEntry, double timeNow, void *data)
- // Dummy method to invoke slideABit method in response to tile sliding timeout
- {
- [(id)data slideABit];
- }
-
- - (void) slideABit
- // Slide tiles one step; re-arrange position of tiles when done sliding.
- // Check for puzzle solved, congratulate and re-shuffle if it is
- {
- int distance,i,direction;
- int row,col,difficulty;
- char str[100];
-
- currentStep++;
- if (howManyToMove < 0)
- direction = (-1);
- else
- direction = 1;
-
- if (currentStep == movementSteps) { // done sliding; re-arrange position of tiles
- DPSRemoveTimedEntry (slideTimer);
- sliding = (BOOL)NO;
- if (slideRowOrCol == ROW) {
- for (i=(fromCol + howManyToMove);i!= fromCol;i=i-direction) {
- [tile[tileInPosition[fromRow][i-direction].tileNumber] addSubview:clearBox];
- [clearBox display];
- [clearBox removeFromSuperview];
- tileInPosition[fromRow][i].tileNumber = tileInPosition[fromRow][i-direction].tileNumber;
- [tile[tileInPosition[fromRow][i].tileNumber] moveTo:
- (tileInPosition[fromRow][i].xPosition) :(tileInPosition[fromRow][i].yPosition)];
- [tile[tileInPosition[fromRow][i].tileNumber] display];
- }
- }
- else {
- for (i=(fromRow + howManyToMove);i!= fromRow;i=i-direction) {
- [tile[tileInPosition[i-direction][fromCol].tileNumber] addSubview:clearBox];
- [clearBox display];
- [clearBox removeFromSuperview];
- tileInPosition[i][fromCol].tileNumber = tileInPosition[i-direction][fromCol].tileNumber;
- [tile[tileInPosition[i][fromCol].tileNumber] moveTo:
- (tileInPosition[i][fromCol].xPosition) :(tileInPosition[i][fromCol].yPosition)];
- [tile[tileInPosition[i][fromCol].tileNumber] display];
- }
- }
- tileInPosition[fromRow][fromCol].tileNumber = NumTilesX*NumTilesY-1;
- // check for puzzle solved
- solved = YES;
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++)
- if (tileInPosition[row][col].tileNumber != (row * NumTilesX + col)) solved = NO;
- rowOfSpace = fromRow;
- colOfSpace = fromCol;
- if (solved == YES) {
- [tileInPosition[NumTilesY-1][NumTilesX-1].invisibleID removeFromSuperview];
- [tileBox addSubview:tile[NumTilesX*NumTilesY-1]];
- [tileBox addSubview:tileInPosition[NumTilesY-1][NumTilesX-1].invisibleID];
- [tile[NumTilesX*NumTilesY-1] moveTo:(tileInPosition[NumTilesY-1][NumTilesX-1].xPosition)
- :(tileInPosition [NumTilesY-1][NumTilesX-1].yPosition)];
- [tile[NumTilesX*NumTilesY-1] display];
- [self playFinishMusic];
- difficulty = NumTilesX-3;
- if ((mixPattern != RANDOMFLAG) && (numberOfMoves < bests[difficulty][mixPattern])) {
- bests[difficulty][mixPattern] = numberOfMoves;
- [[bestsMatrixOut cellAt:difficulty:mixPattern]
- setIntValue:bests[difficulty][mixPattern]];
- [[bestsMatrixOut cellAt:difficulty:mixPattern] setBackgroundGray:(float)NX_WHITE];
- [bestsPanelOut orderFront:self];
- NXRunAlertPanel("Congratulations!",
- "You have set a new 'best'!", "Yippee!", NULL, NULL);
- switch (difficulty) {
- case 0:
- sprintf (str, "%d %d %d %d\0",
- bests[0][0], bests[0][1], bests[0][2], bests[0][3]);
- NXWriteDefault ([NXApp appName], "EasyBests", str);
- break;
- case 1:
- sprintf (str, "%d %d %d %d\0",
- bests[1][0], bests[1][1], bests[1][2], bests[1][3]);
- NXWriteDefault ([NXApp appName], "NormalBests", str);
- break;
- case 2:
- sprintf (str, "%d %d %d %d\0",
- bests[2][0], bests[2][1], bests[2][2], bests[2][3]);
- NXWriteDefault ([NXApp appName], "HardBests", str);
- break;
- }
- [[bestsMatrixOut cellAt:difficulty:mixPattern] setBackgroundGray:(float)NX_LTGRAY];
- }
- }
- currentStep = 0;
- }
- else
- if (slideRowOrCol == ROW) { // not done sliding; move tiles one step
- distance = (int) (((float)currentStep/(float)movementSteps) * sizex * direction);
- for (i=(fromCol + howManyToMove-direction);i!= fromCol-direction;i=i-direction) {
- [tile[tileInPosition[fromRow][i].tileNumber] addSubview:clearBox];
- [clearBox display];
- [clearBox removeFromSuperview];
- [tile[tileInPosition[fromRow][i].tileNumber] moveTo:
- (NXCoord)(tileInPosition[fromRow][i].xPosition + distance) :(tileInPosition[fromRow][i].yPosition)];
- [tile[tileInPosition[fromRow][i].tileNumber] display];
- }
- }
- else {
- distance = (int) (((float)currentStep/(float)movementSteps)*sizey*direction * -1);
- for (i=(fromRow + howManyToMove-direction);i!= fromRow-direction;i=i-direction) {
- [tile[tileInPosition[i][fromCol].tileNumber] addSubview:clearBox];
- [clearBox display];
- [clearBox removeFromSuperview];
- [tile[tileInPosition[i][fromCol].tileNumber] moveTo:
- (tileInPosition[i][fromCol].xPosition) :(NXCoord)(tileInPosition[i][fromCol].yPosition + distance)];
- [tile[tileInPosition[i][fromCol].tileNumber] display];
- }
- }
- return;
- }
-
- - playFinishMusic
- // plays the run of notes when the puzzle is solved
- {
-
-
-
- return self;
- }
-
- - selectSize:sender
- // Receives action messages from the "Difficulty" menu cells
- // Selects x and y size of tile board (currently the same) and invokes "shuffle:"
- {
- int row,col;
- char str[3];
-
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++){
- [tile[tileInPosition[row][col].tileNumber] removeFromSuperview];
- [tileInPosition[row][col].invisibleID removeFromSuperview];
- }
- [tileBox setBorderType:NX_NOBORDER]; // clear old board
- [tileSlideCV display];
-
- // set number of tiles
- NumTilesX = [[sender selectedCell] tag];
- NumTilesY = NumTilesX;
-
- sprintf (str, "%d\0", NumTilesX);
- NXWriteDefault ([NXApp appName], "Difficulty", str);
-
- for (row=0; row<NumTilesY; row++)
- for (col=0; col<NumTilesX; col++) {
- tileInPosition[row][col].tileNumber = (row * NumTilesX)+ col;
- [tileBox addSubview:tile[tileInPosition[row][col].tileNumber]];
- }
- [tile[NumTilesX*NumTilesY-1] removeFromSuperview];
- for (row=0; row<NumTilesY; row++) // invisible buttons added last so they are on top!
- for (col=0; col<NumTilesX; col++) {
- [tileBox addSubview:tileInPosition[row][col].invisibleID];
- }
- [tile[NumTilesX*NumTilesY-1] removeFromSuperview];
-
- if (mixPattern != RANDOMFLAG) {
- [self shuffle:mixPattern];
- [self sizeSlideBoard];
- [slideWindowOut orderFront:self];
- }
- else {
- [self shuffle:mixPattern];
- [self randomMix:self];
- }
- return self;
- }
-
- - shuffle:(int)pattern
- // shuffle the board (to a default shuffled pattern) and reset numberOfMoves
- // Currently, this only handles 3*3, 4*4, and 5*5 boards
- {
- static int scrambledSize5[NUMOFCHALLENGES][25]=
- {{24,10,8,22,5,7,15,16,20,23,17,12,2,14,6,3,13,4,18,1,0,19,11,9,21},
- {24,10,14,13,22,9,21,1,3,11,4,8,16,23,18,2,6,20,15,17,0,7,19,5,12},
- {24,19,14,9,4,23,18,13,8,3,22,17,12,7,2,21,16,11,6,1,20,15,10,5,0},
- {24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}};
- static int scrambledSize4[NUMOFCHALLENGES][16]=
- {{15,14,3,13,0,9,11,4,10,7,5,2,8,1,6,12},
- {15,3,5,13,1,11,12,2,4,10,0,8,6,14,9,7},
- {15,11,7,3,14,10,6,2,13,9,5,1,12,8,4,0},
- {15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}};
- static int scrambledSize3[NUMOFCHALLENGES][9]=
- {{8,0,4,2,5,3,6,1,7},
- {8,1,5,6,4,3,0,2,7},
- {8,5,2,6,4,1,7,3,0},
- {8,1,5,7,4,6,2,3,0}};
- int row,col,patternUsed;
-
- patternUsed = pattern % NUMOFCHALLENGES;
- switch (NumTilesX) {
- case 3:
- for (row=0;row<NumTilesY;row++)
- for (col=0;col<NumTilesX;col++)
- tileInPosition[row][col].tileNumber =
- (int)scrambledSize3[patternUsed][col + (row *NumTilesX)];
- break;
- case 4:
- for (row=0;row<NumTilesY;row++)
- for (col=0;col<NumTilesX;col++)
- tileInPosition[row][col].tileNumber =
- (int)scrambledSize4[patternUsed][col + (row *NumTilesX)];
- break;
- case 5:
- for (row=0;row<NumTilesY;row++)
- for (col=0;col<NumTilesX;col++)
- tileInPosition[row][col].tileNumber =
- (int)scrambledSize5[patternUsed][col + (row *NumTilesX)];
- break;
- }
- rowOfSpace = 0;
- colOfSpace = 0;
-
- numberOfMoves = 0;
- [movesOut setIntValue:numberOfMoves];
- solved = NO;
-
- return self;
- }
-
- - challengeMix:sender
- // select next pattern, call shuffle, change window title, and redraw
- // target of the 'Challenge Mix' menu item
- {
- char buffer[30],bufferNum[5];
-
- [tile[NumTilesX*NumTilesY-1] removeFromSuperview];
- solved = NO;
- if (mixPattern == RANDOMFLAG)
- mixPattern = NUMOFCHALLENGES; // wraps around when used
- mixPattern ++;
- if (mixPattern >= NUMOFCHALLENGES) mixPattern = 0;
-
- [self shuffle:mixPattern];
- strcpy(buffer,"Tile Slide - Challenge#");
- sprintf(bufferNum," %d", (mixPattern+1));
- strcat(buffer,bufferNum);
- [slideWindowOut setTitle:(const char *)buffer];
- [self sizeSlideBoard];
- [slideWindowOut orderFront:self];
-
- return self;
- }
-
- - randomMix:sender
- // make 100 random slides, change window title, and redraw
- // target of the 'Random Mix' menu item
- {
- int i,dir,tempRow,tempCol;
- int tempTileNum;
- static int moves[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
- char buffer[30];
-
- [tile[NumTilesX*NumTilesY-1] removeFromSuperview];
- solved = NO;
- mixPattern = RANDOMFLAG;
- for (i=0;i<100;i++) {
- dir = random() % 4;
- while ((rowOfSpace+moves[dir][1] >= NumTilesY) || (rowOfSpace+moves[dir][1] < 0) ||
- (colOfSpace+moves[dir][0] >= NumTilesX) || (colOfSpace+moves[dir][0] < 0))
- dir = random() % 4;
- tempRow = rowOfSpace+ moves[dir][1];
- tempCol = colOfSpace+moves[dir][0];
- tempTileNum = tileInPosition[tempRow][tempCol].tileNumber;
- tileInPosition[tempRow][tempCol].tileNumber = tileInPosition[rowOfSpace][colOfSpace].tileNumber;
- tileInPosition[rowOfSpace][colOfSpace].tileNumber = tempTileNum;
- rowOfSpace = tempRow;
- colOfSpace = tempCol;
- }
- strcpy(buffer,"Tile Slide - Random");
- [slideWindowOut setTitle:(const char *)buffer];
- numberOfMoves = 0;
- [movesOut setIntValue:numberOfMoves];
- [self sizeSlideBoard];
- [slideWindowOut orderFront:self];
- return self;
- }
-
- - (int)iconEntered:(int)windowNum at:(double)x :(double)y
- iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
- iconWidth:(double)iconWidth iconHeight:(double)iconHeight
- pathList:(char *)pathList
- // respond as delegate for main window
- {
- char *stringPosition;
- int length;
-
- /* save the file's path for later */
- length = strlen(pathList);
- if (filePathLength <= length) {
- if (filePath) {
- free(filePath);
- }
- filePath = (char *)malloc(length + 1);
- filePathLength = length;
- }
- strcpy(filePath, pathList);
- stringPosition = filePath;
-
- /* the number of tabs + 1 equals the number of files dragged in */
- files = 1;
- while (stringPosition = index(stringPosition, '\t')) {
- files++;
- stringPosition++;
- }
-
- return 0;
- }
-
- - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
- // respond as delegate for main window
- {
- NXSize theSize;
-
- if (files!=1) {
- *flag = 0; // tell Workspace to animate the ICON back to its source window
- return 0;
- }
-
- if ([self initAndCheckPicture:(char *)filePath] == NO) {
- *flag = 0; // do not accept the icon
- }
- else {
- *flag = 1; // accept the icon
- if (usePictures == NO){
- usePictures = YES;
- [usePicturesSwitch setIntValue:1];
- NXWriteDefault ([NXApp appName], "UsePictures", "YES");
- }
- [picture getSize:&theSize];
- [picture getSize:(NXSize *)&theSize];
- if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) {
- [useNumbersSwitch setEnabled:YES];
- if (useNumbers == YES)
- [usePicturesSwitch setEnabled:YES];
- }
- else
- { // picture too small to allow using numbers
- useNumbers = NO;
- [useNumbersSwitch setEnabled:NO];
- [useNumbersSwitch setIntValue:0];
- [usePicturesSwitch setEnabled:NO];
- }
- [self sizeSlideBoard];
- }
- return 0;
- }
-
- - (BOOL)initAndCheckPicture:(char *)pictureName
- // Checks out the dragged in icon. Makes sure it is tiff or eps
- // and resizes if too big or small. Returns YES if it loads a picture.
- {
- NXStream *input;
- id newPicture;
- NXSize theSize,tempSize;
- NXRect tempRect;
- int tempPictureType;
- char *rightmostPeriod;
-
- if (pictureName == NULL) return NO;
- rightmostPeriod = strrchr(pictureName, '.');
- if (rightmostPeriod == NULL) return NO;
- tempPictureType = TYPENONE;
- if (NXOrderStrings((unsigned char *) ".tiff",(unsigned char *) rightmostPeriod, YES, 5, NULL) == 0)
- tempPictureType = TYPETIFF;
- if (NXOrderStrings((unsigned char *) ".eps",(unsigned char *) rightmostPeriod, YES, 4, NULL) == 0)
- tempPictureType = TYPEEPS;
- if (tempPictureType == TYPENONE) return NO; // unsuccessful load
-
- input = NXMapFile(pictureName, NX_READONLY);
- if (input == NULL) return NO; // unsuccessful load
-
- newPicture = [[NXImage alloc] initFromStream:input];
- [newPicture getSize:(NXSize *)&theSize];
-
- /* if a tiff picture is too small, it is rejected */
- /* (We could just resize it, as we do for eps pictures, */
- /* but I don't like the look of resized tiffs anyways.) */
- if (((theSize.height < IMAGEMINHEIGHT) || (theSize.width < IMAGEMINWIDTH))
- && (tempPictureType == TYPETIFF))
- {
- [newPicture free];
- NXCloseMemory(input,NX_FREEBUFFER);
- return NO; // unsuccessful load
- }
-
- /* we now know we have a valid picture, so get rid of the old one */
- [picture free];
- picture = newPicture;
-
- /* if a tiff picture is too large, use only the bottom left section */
- if (((theSize.height > IMAGEMAXHEIGHT) || (theSize.width > IMAGEMAXWIDTH))
- && (tempPictureType == TYPETIFF))
- {
- if (theSize.width > IMAGEMAXWIDTH)
- tempRect.size.width = IMAGEMAXWIDTH;
- else
- tempRect.size.width = theSize.width;
- if (theSize.height > IMAGEMAXHEIGHT)
- tempRect.size.height = IMAGEMAXHEIGHT;
- else
- tempRect.size.height = theSize.height;
- tempRect.origin.x = 0;
- tempRect.origin.y = 0;
- picture = [[NXImage alloc] initFromImage:(NXImage *)newPicture rect:(const NXRect *)&tempRect];
- }
-
- /* if an eps picture is too small, we resize it */
- if (((theSize.height < IMAGEMINHEIGHT) || (theSize.width < IMAGEMINWIDTH))
- && (tempPictureType == TYPEEPS))
- {
- if (theSize.width < IMAGEMINWIDTH)
- tempSize.width = IMAGEMINWIDTH;
- else
- tempSize.width = theSize.width;
- if (theSize.height < IMAGEMINHEIGHT)
- tempSize.height = IMAGEMINHEIGHT;
- else
- tempSize.height = theSize.height;
- [newPicture setSize:&tempSize];
- }
-
- /* if an eps picture is too large, we resize it */
- if (((theSize.height > IMAGEMAXHEIGHT) || (theSize.width > IMAGEMAXWIDTH))
- && (tempPictureType == TYPEEPS))
- {
- if (theSize.width > IMAGEMAXWIDTH)
- tempSize.width = IMAGEMAXWIDTH;
- else
- tempSize.width = theSize.width;
- if (theSize.height > IMAGEMAXHEIGHT)
- tempSize.height = IMAGEMAXHEIGHT;
- else
- tempSize.height = theSize.height;
- [newPicture setSize:&tempSize];
- }
-
- /* set eps pictures to be scalable */
- if (tempPictureType == TYPEEPS) {
- [picture setScalable:(BOOL)YES];
- [picture setDataRetained:(BOOL)YES];
- }
-
- pictureType = tempPictureType;
- pictureLoaded = YES;
- NXCloseMemory(input,NX_FREEBUFFER);
- NXWriteDefault ([NXApp appName], "Picture", filePath);
- return YES;
- }
-
- - windowWillResize:sender toSize:(NXSize *)frameSize
- // Acts as delegate for main window confining window resizing
- {
- if ( useNumbers == YES)
- {
- if (frameSize->width < (NXCoord)(NUMBERMINWIDTH+leftMargin+rightMargin+2))
- frameSize->width = (NXCoord)(NUMBERMINWIDTH+leftMargin+rightMargin+2);
- if (frameSize->height < (NXCoord)(NUMBERMINHEIGHT+23+9+topMargin+bottomMargin))
- frameSize->height = (NXCoord)(NUMBERMINHEIGHT+23+9+topMargin+bottomMargin);
- }
- if ( usePictures == YES)
- if (pictureType == TYPEEPS) {
- if (frameSize->width < (NXCoord)(IMAGEMINWIDTH+leftMargin+rightMargin+2))
- frameSize->width = (NXCoord)(IMAGEMINWIDTH+leftMargin+rightMargin+2);
- if (frameSize->height < (NXCoord)(IMAGEMINHEIGHT+23+9+topMargin+bottomMargin))
- frameSize->height = (NXCoord)(IMAGEMINHEIGHT+23+9+topMargin+bottomMargin);
- if (frameSize->width > (NXCoord)(IMAGEMAXWIDTH+leftMargin+rightMargin+2))
- frameSize->width = (NXCoord)(IMAGEMAXWIDTH+leftMargin+rightMargin+2);
- if (frameSize->height > (NXCoord)(IMAGEMAXHEIGHT+23+9+topMargin+bottomMargin))
- frameSize->height = (NXCoord)(IMAGEMAXHEIGHT+23+9+topMargin+bottomMargin);
- }
- else { // I don't like the look of resized tiffs, so don't allow resizing
- frameSize->width = (windowSize.width+2);
- frameSize->height = (windowSize.height+23+9);
- }
- return self;
- }
-
- - windowDidResize:sender;
- // Acts as delegate for main window. Enable or disable usePicturesSwitch and
- // useNumbersSwitch according to new size. Resize picture and redraw.
- {
- NXRect theRect;
- NXSize theSize;
-
- [tileSlideCV getFrame:(NXRect *)&theRect];
- theSize.width = theRect.size.width - leftMargin - rightMargin;
- theSize.height = theRect.size.height - topMargin - bottomMargin;
-
- if ((theSize.height >= IMAGEMINHEIGHT) && (theSize.width >= IMAGEMINWIDTH)
- && (theSize.height <= IMAGEMAXHEIGHT) && (theSize.width <= IMAGEMAXWIDTH)
- && (pictureLoaded == YES) && (useNumbers == TRUE))
- [usePicturesSwitch setEnabled:YES];
- else
- [usePicturesSwitch setEnabled:NO];
-
- if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)
- && (usePictures == TRUE))
- [useNumbersSwitch setEnabled:YES];
- else
- [useNumbersSwitch setEnabled:NO];
-
- if ( usePictures == NO) {
- if (pictureType == TYPEEPS)
- [picture setSize:&theSize];
- [self sizeSlideBoard];
- }
- else
- if (pictureType == TYPEEPS) {
- [picture setSize:&theSize];
- [self sizeSlideBoard];
- }
-
- return self;
- }
-
- @end
-